﻿using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows;
using System.Windows.Media;
using System.Collections.Generic;
using System.Windows.Input;

namespace TwoPageView3 {
    public class VirtualizingTilePanel : Panel, IScrollInfo {
        private Canvas mContent;
        private TranslateTransform mTrans = new TranslateTransform();
        private Point mOffset;
        private ScrollViewer mOwner;
        private bool mCanHScroll = false;
        private bool mCanVScroll = false;
        private Size mExtent;
        private Size mViewport;
        private List<IVirtualTile> mVirtualChildList = new List<IVirtualTile>();
        private Size mTileSize = new Size(1, 1);

        public VirtualizingTilePanel() {
            RenderTransform = mTrans;
            mContent = new Canvas();
            Children.Add(mContent);
        }

        public void UpdateTileSize(Size tileSize) {
            mTileSize = tileSize;

            foreach (var c in mVirtualChildList) {
                c.Resize(tileSize);
            }
        }
        
        /// <summary>
        /// タイルのサイズ (ローカル座標系、スケーリング前のサイズ)
        /// </summary>
        public Size TileSize {
            get { return mTileSize; }
        }

        public void ClearChildren() {
            foreach (var c in mVirtualChildList) {
                mContent.Children.Remove(c.Visual);
                c.DisposeVisual();
            }
            mVirtualChildList.Clear();
            mContent.Children.Clear();

            mOffset.Y = 0;
            mTrans.Y = 0;
            mExtent = mViewport;
        }

        public void AddVirtualChild(IVirtualTile child) {
            mVirtualChildList.Add(child);
        }
        
        private bool UpdateViewportAndExtent(Size newViewportSize, out Size totalChildrenSize, out int countW) {
            bool updated = false;

            countW = (int)(newViewportSize.Width / mTileSize.Width);
            if (countW <= 0) {
                countW = 1;
            }

            int countH = (mVirtualChildList.Count + countW - 1) / countW;

            totalChildrenSize = new Size(
                    countW * mTileSize.Width,
                    countH * mTileSize.Height);

            if (totalChildrenSize != mExtent) {
                mExtent = totalChildrenSize;
                updated = true;
            }

            if (newViewportSize != mViewport) {
                mViewport = newViewportSize;
                updated = true;
            }

            return updated;
        }

        public Size MeasureOptimalTileSize() {
            Size result = new Size(0, 0);
            int count = 0;
            foreach (var c in mVirtualChildList) {
                var childSize = c.DesirableSize();
                if (!childSize.IsEmpty) {
                    result = Utility.Union(result, childSize);
                    ++count;
                }
            }
            if (result.Width == 0 || result.Height == 0) {
                return mTileSize;
            }
            return result;
        }

        protected override Size MeasureOverride(Size availableSize) {
            Size childSize;
            int countW;
            bool updated = UpdateViewportAndExtent(availableSize, out childSize, out countW);
            if (updated && mOwner != null) {
                mOwner.InvalidateScrollInfo();
            }

            foreach (UIElement child in InternalChildren) {
                child.Measure(childSize);
            }

            return availableSize;
        }

        public void UpdateItemPosition() {
            UpdateChildPosition(mViewport);
        }

        private void UpdateChildPosition(Size finalSize) {
            Size childSize;
            int countW;
            bool updated = UpdateViewportAndExtent(finalSize, out childSize, out countW);
            if (updated && mOwner != null) {
                mOwner.InvalidateScrollInfo();
            }

            mContent.Width = Math.Max(mContent.MinWidth, mExtent.Width);
            mContent.Height = Math.Max(mContent.MinHeight, mExtent.Height);
            mContent.Arrange(new Rect(0, 0, mContent.Width, mContent.Height));

            List<IVirtualTile>.Enumerator ite = mVirtualChildList.GetEnumerator();
            for (int i=0; i < mVirtualChildList.Count; ++i) {
                int x = i % countW;
                int y = i / countW;

                ite.MoveNext();
                var c = ite.Current;

                // スクリーン座標系で比較。
                double viewPortTop    = VerticalOffset;
                double viewportBottom = VerticalOffset + ViewportHeight;
                double tileTop        =  y * mTileSize.Height;
                double tileBottom     = (y + 1) * mTileSize.Height;

                if (viewPortTop <= tileBottom &&
                        tileTop <= viewportBottom) {
                    // タイルが一部分でも画面内に存在する。
                    var uie = c.Visual;
                    if (null == uie) {
                        uie = c.CreateVisual(this);
                        mContent.Children.Add(uie);
                    }
                    double left = x * mTileSize.Width;
                    if (Canvas.GetLeft(uie) != left) {
                        Canvas.SetLeft(uie, left);
                    }
                    double top = y * mTileSize.Height;
                    if (Canvas.GetTop(uie) != top) {
                        Canvas.SetTop(uie, top);
                    }
                } else {
                    // タイルが完全に画面外。
                    var uie = c.Visual;
                    if (uie != null) {
                        mContent.Children.Remove(uie);
                        c.DisposeVisual();
                    }
                }
            }
        }

        protected override Size ArrangeOverride(Size finalSize) {
            UpdateChildPosition(finalSize);
            SetVerticalOffset(mOffset.Y);

            return finalSize;
        }

        Rect IScrollInfo.MakeVisible(Visual visual, Rect rectangle) {
            return rectangle;
        }

        public void SetVerticalOffset(double offset) {
            Console.WriteLine("SetVerticalOffset({0})", offset);


            if (offset < 0 || mViewport.Height >= mExtent.Height) {
                offset = 0;
            } else {
                if (offset + mViewport.Height >= mExtent.Height) {
                    offset = mExtent.Height - mViewport.Height;
                }
            }

            mOffset.Y = offset;
            mTrans.Y = -offset;

            UpdateItemPosition();
            if (mOwner != null) {
                mOwner.InvalidateScrollInfo();
            }
        }

        public ScrollViewer ScrollOwner {
            get { return mOwner; }
            set { mOwner = value; }
        }

        bool IScrollInfo.CanHorizontallyScroll {
            get { return mCanHScroll; }
            set { mCanHScroll = value; }
        }

        bool IScrollInfo.CanVerticallyScroll {
            get { return mCanVScroll; }
            set { mCanVScroll = value; }
        }

        double IScrollInfo.HorizontalOffset {
            get { return mOffset.X; }
        }

        public double VerticalOffset {
            get { return mOffset.Y; }
        }

        double IScrollInfo.ExtentHeight {
            get { return mExtent.Height; }
        }

        double IScrollInfo.ExtentWidth {
            get { return mExtent.Width; }
        }

        public double ViewportHeight {
            get { return mViewport.Height; }
        }

        double IScrollInfo.ViewportWidth {
            get { return mViewport.Width; }
        }

        void IScrollInfo.LineUp() {
            Console.WriteLine("LineUp()");
            SetVerticalOffset(VerticalOffset - 1);
        }

        void IScrollInfo.LineDown() {
            Console.WriteLine("LineDown()");
            SetVerticalOffset(VerticalOffset + 1);
        }

        void IScrollInfo.MouseWheelUp() {
            Console.WriteLine("MouseWheelUp()");
            SetVerticalOffset(VerticalOffset - mTileSize.Height);
        }

        void IScrollInfo.MouseWheelDown() {
            Console.WriteLine("MouseWheelDown()");
            SetVerticalOffset(VerticalOffset + mTileSize.Height);
        }

        void IScrollInfo.PageUp() {
            Console.WriteLine("PageUp()");
            double childHeight = mViewport.Height / InternalChildren.Count;
            SetVerticalOffset(VerticalOffset - childHeight);
        }

        void IScrollInfo.PageDown() {
            Console.WriteLine("PageDown()");
            double childHeight = mViewport.Height / InternalChildren.Count;
            SetVerticalOffset(VerticalOffset + childHeight);
        }

        void IScrollInfo.SetHorizontalOffset(double offset) {
        }

        void IScrollInfo.LineLeft() {
        }

        void IScrollInfo.LineRight() {
        }

        void IScrollInfo.MouseWheelLeft() {
        }

        void IScrollInfo.MouseWheelRight() {
        }

        void IScrollInfo.PageLeft() {
        }

        void IScrollInfo.PageRight() {
        }
    }
}
